# -*- coding: utf-8 -*-
"""
Membangun basis data Capaian Pembelajaran (cp_db.json) dari hasil ekstraksi
Kepkabadan Kemendikdasmen No. 046/H/KR/2025.

Sumber:
  /data/lamp2.txt  -> Lampiran II (SD/MI, SMP/MTs, SMA/MA)  = mapel umum
  /data/lamp3.txt  -> Lampiran III (SMK/MAK)                = mapel kejuruan
"""
import json
import re
import os

ZW = dict.fromkeys(map(ord, "\u200b\u200c\u200d\ufeff\u00ad"), None)


def clean(s):
    s = s.translate(ZW)
    s = s.replace("\u00a0", " ")
    s = s.replace("\x0c", "")
    return s


def load_lines(path):
    with open(path, encoding="utf-8") as f:
        return [clean(l.rstrip("\n")) for l in f]


HEAD_RE = re.compile(r"^\s*((?:[IVXLC]+)(?:\.\d+)?)\.?\s+CAPAIAN PEMBELAJARAN\s*(.*)$")
SEC_RE = re.compile(r"^\s*([A-D])\.\s+(Rasional|Tujuan|Karakteristik|Capaian Pembelajaran)\s*$")
FASE_ITEM_RE = re.compile(r"^\s*(\d+)\.\s*Fase\s+([A-F])\b")
FASE_LINE_RE = re.compile(r"Pada akhir Fase\s+([A-F])\b")


def squash(lines):
    """Gabungkan baris terlipat menjadi satu paragraf rapi."""
    txt = " ".join(l.strip() for l in lines)
    txt = re.sub(r"\s+", " ", txt).strip()
    return txt


def find_sections(lines, start, end):
    """Kembalikan dict huruf-bagian -> (awal, akhir)."""
    marks = []
    for i in range(start, end):
        m = SEC_RE.match(lines[i])
        if m:
            marks.append((i, m.group(1)))
    out = {}
    for idx, (i, letter) in enumerate(marks):
        stop = marks[idx + 1][0] if idx + 1 < len(marks) else end
        if letter not in out:
            out[letter] = (i + 1, stop)
    return out


def _deteksi_kolom(lines, a, b):
    """Tentukan indeks awal kolom kanan (Deskripsi) dari histogram celah spasi."""
    from collections import Counter
    cnt = Counter()
    for i in range(a, b):
        ln = lines[i]
        if not ln.strip():
            continue
        for m in re.finditer(r"\S {3,}(?=\S)", ln):
            cnt[m.end()] += 1
        m = re.match(r"^( {6,})\S", ln)
        if m:
            cnt[len(m.group(1))] += 1
    if not cnt:
        return None
    # gabungkan posisi yang berdekatan (toleransi 1 karakter)
    gab = Counter()
    for pos, c in cnt.items():
        gab[pos] += c
        gab[pos + 1] += c * 0.5
        gab[pos - 1] += c * 0.5
    best, score = max(gab.items(), key=lambda kv: (kv[1], -kv[0]))
    if score < 3:
        return None
    return best


def parse_karakteristik(lines, a, b):
    """Ambil pasangan Elemen -> Deskripsi dari tabel dua kolom bagian C."""
    rows = []
    head_i = None
    for i in range(a, b):
        if re.search(r"Elemen\s{2,}Deskripsi", lines[i]):
            head_i = i
            break
    if head_i is None:
        return rows

    split_col = _deteksi_kolom(lines, head_i, b)
    if not split_col:
        return rows

    cur_name, cur_desc = [], []
    prev_left_empty = True
    for i in range(head_i + 1, b):
        ln = lines[i]
        if not ln.strip():
            continue
        if re.search(r"Elemen\s{2,}Deskripsi", ln):
            prev_left_empty = True
            continue
        left = ln[:split_col].rstrip()
        right = ln[split_col:].strip() if len(ln) > split_col else ""
        left_s = left.strip()
        if left_s and prev_left_empty and (cur_name or cur_desc):
            rows.append((squash(cur_name), squash(cur_desc)))
            cur_name, cur_desc = [], []
        if left_s:
            cur_name.append(left_s)
        if right:
            cur_desc.append(right)
        prev_left_empty = not bool(left_s)
    if cur_name or cur_desc:
        rows.append((squash(cur_name), squash(cur_desc)))

    out = []
    for n, d in rows:
        n = re.sub(r"\s+", " ", n).strip(" .:")
        d = re.sub(r"\s+", " ", d).strip()
        if n and 2 < len(n) < 130 and len(d) > 15:
            out.append({"elemen": n, "deskripsi": d})
    return out


def parse_cp_block(lines, a, b, prefix=None):
    """
    Parse daftar bernomor pada bagian D.
    prefix=None  -> item berbentuk '1.  Nama Elemen'   (pola SMK / Lampiran III)
    prefix=N     -> item berbentuk 'N.1.  Nama Elemen' (pola Lampiran II)
    """
    if prefix is None:
        item_re = re.compile(r"^\s*(\d+)\.\s+(\S.*)$")
    else:
        item_re = re.compile(r"^\s*%d\.(\d+)\.\s*(\S.*)$" % prefix)
    items = []
    cur = None
    for i in range(a, b):
        ln = lines[i]
        m = item_re.match(ln)
        if m:
            if cur:
                items.append(cur)
            cur = {"no": int(m.group(1)), "elemen": m.group(2).strip(), "buf": []}
        elif cur is not None:
            if ln.strip():
                cur["buf"].append(ln)
    if cur:
        items.append(cur)

    out = []
    for it in items:
        name = re.sub(r"\s+", " ", it["elemen"]).strip()
        body = squash(it["buf"])
        # kadang nama elemen terlipat ke baris berikut (tanpa titik akhir & diakhiri kata sambung)
        if body and name.endswith(("dan", "serta", "(Thinking", "and")):
            parts = body.split(" ", 3)
        # jika nama elemen ternyata sudah berisi kalimat CP (tidak ada body)
        if not body:
            body = name
        out.append({"elemen": name.rstrip(":"), "cp": body})
    return out


def parse_file(path, lampiran, jenjang):
    lines = load_lines(path)
    n = len(lines)
    heads = []
    for i, ln in enumerate(lines):
        m = HEAD_RE.match(ln)
        if m:
            heads.append((i, m.group(1), m.group(2).strip()))
    subjects = []
    for idx, (i, kode, title) in enumerate(heads):
        end = heads[idx + 1][0] if idx + 1 < len(heads) else n
        # judul bisa terlipat ke baris berikutnya (huruf kapital semua)
        j = i + 1
        while j < end and j < i + 4:
            nxt = lines[j].strip()
            if nxt and nxt == nxt.upper() and not SEC_RE.match(lines[j]) \
               and re.match(r"^[A-Z0-9 ,()/\-\.]+$", nxt) and len(nxt) > 2:
                title = (title + " " + nxt).strip()
                j += 1
            else:
                break
        title = re.sub(r"\s+", " ", title).strip()
        if not title:
            continue
        secs = find_sections(lines, i, end)
        karak = []
        if "C" in secs:
            karak = parse_karakteristik(lines, secs["C"][0], secs["C"][1])
        rasional = ""
        if "A" in secs:
            rasional = squash(lines[secs["A"][0]:min(secs["A"][1], secs["A"][0] + 60)])
        fases = {}
        if "D" in secs:
            da, db = secs["D"]
            fase_marks = []
            for k in range(da, db):
                m = FASE_ITEM_RE.match(lines[k])
                if m:
                    fase_marks.append((k, int(m.group(1)), m.group(2)))
            if fase_marks:
                for fi, (k, num, huruf) in enumerate(fase_marks):
                    stop = fase_marks[fi + 1][0] if fi + 1 < len(fase_marks) else db
                    els = parse_cp_block(lines, k, stop, prefix=num)
                    if els:
                        fases[huruf] = els
            else:
                # pola SMK: langsung 'Pada akhir Fase X, murid ...'
                starts = [(k, FASE_LINE_RE.search(lines[k]).group(1))
                          for k in range(da, db) if FASE_LINE_RE.search(lines[k])]
                if starts:
                    for fi, (k, huruf) in enumerate(starts):
                        stop = starts[fi + 1][0] if fi + 1 < len(starts) else db
                        els = parse_cp_block(lines, k + 1, stop, prefix=None)
                        if els:
                            fases[huruf] = els
        subjects.append({
            "kode": kode,
            "nama": title.title() if title.isupper() else title,
            "nama_asli": title,
            "lampiran": lampiran,
            "jenjang": jenjang,
            "rasional": rasional[:1200],
            "karakteristik": karak,
            "fase": fases,
        })
    return subjects


def main():
    db = []
    db += parse_file("/data/lamp2.txt", "II", "UMUM (SD/SMP/SMA/SMK)")
    db += parse_file("/data/lamp3.txt", "III", "SMK/MAK")
    # buang entri tanpa CP sama sekali
    db = [s for s in db if s["fase"]]
    os.makedirs("/data/atpgen", exist_ok=True)
    with open("/data/atpgen/cp_db.json", "w", encoding="utf-8") as f:
        json.dump(db, f, ensure_ascii=False, indent=1)

    print("TOTAL MAPEL :", len(db))
    print("Lampiran II :", sum(1 for s in db if s["lampiran"] == "II"))
    print("Lampiran III:", sum(1 for s in db if s["lampiran"] == "III"))
    print("Punya Fase E:", sum(1 for s in db if "E" in s["fase"]))
    print("Punya Fase F:", sum(1 for s in db if "F" in s["fase"]))
    print()
    for s in db[:5]:
        print(s["lampiran"], s["kode"], "|", s["nama_asli"][:60], "| fase:", list(s["fase"]))
    print("...")
    for s in db[-5:]:
        print(s["lampiran"], s["kode"], "|", s["nama_asli"][:60], "| fase:", list(s["fase"]))


if __name__ == "__main__":
    main()
